//
//  MKAPITask.h
//  MKAPI
//  异步任务基类，可以进行任务生命周期管理，以及对各生命周期状态添加回调处理。对象由全局会话持有，使用者无需进行内存管理。
//  异步任务需要调用run方法，才会将自身添加到全局的任务会话中，进行内存管理。需要调用completeWithError方法进行完成，从而将自身移出任务会话，释放内存。
//  异步任务，定义了四种状态。针对状态变更，可以添加回调block，且回调block支持多次添加。
//  Created by 苏尚进 on 2022/6/18.
//  Copyright © 2022 moon. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
@class MKAPITask,MKAPITaskSession;
typedef enum : NSUInteger {
    MKAPITaskStateCreated,//刚被创建出来
    MKAPITaskStateRunning,//正在执行中
    MKAPITaskStateCanceling,//取消中
    MKAPITaskStateCompleted,//完成
} MKAPITaskState;

typedef void(^MKAPITaskBeginRunningBlock)(__kindof MKAPITask *task);
typedef void(^MKAPITaskProgressBlock)(__kindof MKAPITask *task,NSProgress *progress);
typedef void(^MKAPITasksProgressBlock)(NSArray<__kindof MKAPITask *> *tasks,NSProgress *progress,BOOL isFirst);
typedef void(^MKAPITaskProgressBlock2)(__kindof MKAPITask *task,NSProgress *progress,BOOL isFirst);
typedef void(^MKAPITaskCompleteBlock)(__kindof MKAPITask *task,BOOL success,NSError * _Nullable error);
typedef void(^MKAPITasksCompleteBlock)(NSArray<__kindof MKAPITask *> *tasks,BOOL success,NSError * _Nullable error);
typedef void(^MKAPITaskCompleteBlock2)(__kindof MKAPITask * _Nullable task,BOOL success,NSError * _Nullable error);

@interface MKAPITask : NSObject
@property(readonly) NSUInteger taskIdentifier;//任务唯一id值，由任务会话赋值
@property(nonatomic,readonly,nullable) MKAPITaskSession *taskSession;//所属的任务会话
@property(readonly) MKAPITaskState state;//任务在会话中的状态
@property(nonatomic,readonly,copy,nullable) NSError *error;
@property(nonatomic,readonly) NSProgress *progress;//进度管理，默认TotalUnitCount=100

@property(nonatomic,readonly,nullable) NSDate *beginRunningDate;//开始执行任务的时间
@property(nonatomic,readonly,nullable) NSDate *completedDate;//完成任务的时间

@property(nonatomic,assign) NSTimeInterval timeoutDuration;//超时时间，单位秒，0代表不限。默认为0

/// 任务开始回调,任务开始后，会清空所有回调block
/// @param block 回调
- (NSUInteger)addBeginRunningHandler:(MKAPITaskBeginRunningBlock)block;

/// 完成回调,任务完成时，会清空所有回调block
/// @param block 回调
- (NSUInteger)addCompletionHandler:(MKAPITaskCompleteBlock)block;

/// 进度回调,任务完成时，会清空所有回调block
/// @param block 回调
- (NSUInteger)addProgressHandler:(MKAPITaskProgressBlock)block;

/// 等待其他任务完成，会添加其他任务的进度到自身的progress对象中。
/// @param tasks 任务列表，任务并行执行
/// @param block 完成回调
- (void)waitTasks:(NSArray<MKAPITask *> *)tasks withCompletion:(nullable MKAPITasksCompleteBlock)block;
- (void)waitTask:(MKAPITask *)task withCompletion:(nullable MKAPITaskCompleteBlock2)block;

/// 等待其他任务完成.如果本身没有开始任务时，此时会先执行[self run]
/// @param tasks 任务列表，任务并行执行
/// @param block 完成回调
/// @param progressBlock 其他任务进度变更回调,会将多个任务的进度合并成一个NSProgress对象
- (void)waitTasks:(NSArray<MKAPITask *> *)tasks withCompletion:(nullable MKAPITasksCompleteBlock)block progress:(nullable MKAPITasksProgressBlock)progressBlock;
- (void)waitTask:(MKAPITask *)task withCompletion:(nullable MKAPITaskCompleteBlock)block progress:(nullable MKAPITaskProgressBlock2)progressBlock;
@property(nonatomic,readonly) NSArray<__kindof MKAPITask *> *waittingTasks;//正在等待中的任务列表

/// 取消self.waittingTasks中的任务
- (void)cancelWaittingTasks;

/// 给任务添加标签说明，用于在会话中，根据该标签查询任务. [[MKAPITaskSession sharedSession] getTasksWithTag:@""];
/// 使用场景如：UIViewController，在发起异步任务后，给该任务添加上tag：[task addTag:self.mkapi_addressTag]。然后在viewcontroller的dealloc方法中，通过[[MKAPITaskSession sharedSession] getTasksWithTag:self.mkapi_addressTag]，来获取关联的异步任务，进而取消该任务。
/// - Parameter tag: 标签名称
- (void)addTag:(NSString *)tag;
- (void)removeTag:(NSString *)tag;
- (BOOL)containTag:(NSString *)tag;
@property(nonatomic,readonly) NSArray<NSString *> *tags;

/// 获取本任务类型唯一实例，如果当前串行队列中没有任务时，将会创建一个
/// - Parameter completion: 完成回调用，将会返回本任务类型的实例
+ (void)createSingleTaskWithCompletion:(void(^)(__kindof MKAPITask *task))completion;
@end
NS_ASSUME_NONNULL_END

NS_ASSUME_NONNULL_BEGIN
//保护方法，仅用于子类调用
@interface MKAPITask (Project)
/// 设置任务为完成状态
/// @param error 错误信息
- (void)completeWithError:(nullable NSError *)error;

/// 更新任务进度
/// @param handler 执行进度更新的block
- (void)updateProgress:(void(^ _Nullable)(NSProgress *progress))handler;
@end

//子类重载
@interface MKAPITask (Override)
/// 将要开始执行任务，子类继承。业务代码不要直接调用
- (void)willBeginRunning;

/// 完成任务，子类继承。业务代码不要直接调用
/// @param error 错误信息
- (void)didCompleteWithError:(nullable NSError *)error;

/// 更新进度，子类继承。业务代码不要直接调用
- (void)didUpdateProgress;

/// 执行任务，子类继承。业务代码不要直接调用
- (void)doRunTask;

/// 任务取消时执行的动作，子类继承。cancel动作结束后，需要调用completeTaskWithError方法，来完成任务，error形如[NSError errorWithDomain:@"xx.xx.xx" code:MKAPITaskCodeCancel userInfo:@{NSLocalizedDescriptionKey:@"Cancel"}]。否则任务会一直处于MKAPITaskStateCanceling状态
- (void)doCancel;

@end
NS_ASSUME_NONNULL_END


NS_ASSUME_NONNULL_BEGIN
/// 任务会话，用于持有任务对象
@interface MKAPITaskSession : NSObject
@property(nonatomic,readonly) NSOperationQueue *runningQueue;
+ (instancetype)sharedSession;
- (id)initWithRunningQueue:(NSOperationQueue *)runingQueue;

/// 将在串行队列中执行任务
/// @param task 任务
- (void)runTask:(MKAPITask *)task;

/// 批量取消任务列表
/// @param tasks 任务列表
- (void)cancelTasks:(NSArray<MKAPITask *> *)tasks;

/// 获取指定任务类型的所有实例。支持多线程调用
/// @param taskClass MKAPITask的子类型
/// @param completion 完成回调
- (void)getTasksWithTaskClass:(Class)taskClass completion:(void(^)(NSArray<__kindof MKAPITask *> *tasks))completion;
- (NSArray<__kindof MKAPITask *> *)getTasksWithTaskClass:(Class)taskClass;

/// 获取指定任务标签的所有实例。支持多线程调用
/// @param tag 任务标签
/// @param completion 完成回调
- (void)getTasksWithTag:(NSString *)tag completion:(void(^)(NSArray<__kindof MKAPITask *> *tasks))completion;
- (NSArray<__kindof MKAPITask *> *)getTasksWithTag:(NSString *)tag;

/// 取消指定标签的所有任务
/// @param tag 任务标签
- (void)cancelTasksWithTag:(NSString *)tag;

/// 获取指定任务，如果当前串行队列中没有任务时，将会创建一个
/// @param taskClass MKAPITask的子类型
/// @param completion 完成回调
- (void)createSingleTaskWithTaskClass:(Class)taskClass completion:(void(^)(__kindof MKAPITask *task))completion;

/// 在任务队列中执行block，用于任务代码的串行执行
/// @param block block
- (void)runCodeInQueue:(void(^)(void))block;

- (void)waitTasks:(NSArray<MKAPITask *> *)tasks withCompletion:(MKAPITasksCompleteBlock)block progress:(MKAPITasksProgressBlock)progressBlock;

/// 全局保存任务单例
/// - Parameter task: 任务单例
- (void)saveSharedTask:(nullable MKAPITask *)task;

/// 清除任务类对应的全局单例
/// - Parameter taskClass: 类型对象
- (void)cleanSharedTask:(Class)taskClass;

/// 获取全局的任务单例
/// - Parameter taskClass: 类型对象
- (nullable __kindof MKAPITask *)getSharedTask:(Class)taskClass;
@end
NS_ASSUME_NONNULL_END

NS_ASSUME_NONNULL_BEGIN
/// 任务执行、取消
@interface MKAPITask(MKAPITaskSession)
/// 在全局任务会话中执行，等效于[self runInSession:[MKAPITaskSession sharedSession]]
- (void)run;

/// 在指定任务会话中执行任务，在任务生命周期内，会话将会持有任务。
/// @param session 会话对象
- (void)runInSession:(MKAPITaskSession *)session;

/// 取消任务。如果任务状态为完成或取消中时，不作响应
- (void)cancel;
@end
NS_ASSUME_NONNULL_END

NS_ASSUME_NONNULL_BEGIN
enum : NSUInteger {
    MKAPITaskCodeCancel=-100821001,//取消任务
    MKAPITaskCodeTimeout=-100821002,//任务超时
};

extern NSErrorUserInfoKey const MKAPITaskErrorTaskKey;//=MKAPIRequestErrorUrlKey
@interface NSError(MKAPITask)
@property(nonatomic,readonly) BOOL mkapi_isTaskCancel;
@property(nonatomic,readonly) BOOL mkapi_isTaskTimeout;

/// 生产取消任务的error对象。error的domain为MKAPITask，code=MKAPITaskCodeCancel，userInfo[NSLocalizedDescriptionKey] = @"Cancel Task";
/// - Parameter task: 任务，放在userInfo[MKAPITaskErrorTaskKey]中
+ (NSError *)mkapi_errorWithCancelTask:(MKAPITask *)task;

/// 任务超时
/// - Parameter task: 任务，放在userInfo[MKAPITaskErrorTaskKey]中
+ (NSError *)mkapi_errorWithTimeoutTask:(MKAPITask *)task;
@end
NS_ASSUME_NONNULL_END

NS_ASSUME_NONNULL_BEGIN
@interface NSProgress(MKAPITask)
@property(nonatomic,assign) CGFloat mkapi_progressPercent;//百分比进度,取值为[0.0,1.0]
@property(nonatomic,readonly) int64_t mkapi_fractionCompletedUnitCount;//值为=fractionCompleted*totalUnitCount
@property(nonatomic,readonly) NSString *mkapi_description;

/// 在设置新的完成量与总量时，如果新的完成量>旧的总量，先设置完成量，会导致fractionCompleted计算出错
/// - Parameters:
///   - completedUnitCount: 完成量
///   - totalUnitCount: 总量
- (void)mkapi_setCompletedUnitCount:(int64_t)completedUnitCount totalUnitCount:(int64_t)totalUnitCount;
@end
NS_ASSUME_NONNULL_END

NS_ASSUME_NONNULL_BEGIN
@interface NSObject (MKAPI_TaskTag)
@property(nonatomic,readonly) NSString *mkapi_addressTag;//返回实例的唯一标签字符串，格式如:<类名:内存地址>
/// 运行任务，并将自身的mkapi_addressTag值，赋予任务的tag标签。后续可以使用mkapi_cancelTasks，来取消这些任务。
/// - Parameters:
///   - task: 任务
///   - completion: 完成回调
- (void)mkapi_runTask:(MKAPITask *)task withCompletion:(void(^ _Nullable)(__kindof id obj,__kindof MKAPITask *task,BOOL success,NSError * _Nullable error))completion;

/// 取消任务会话中，含有sefl.mkapi_addressTag标签值的任务
- (void)mkapi_cancelWaitingTasks;
@end
NS_ASSUME_NONNULL_END
